#ifndef BOOSTKERNEL_H
#define BOOSTKERNEL_H

#include <boost/thread.hpp>
#include <algorithm>

#include "../../QueryDataManipulation/src/QueryDataManipulation.hpp"
#include "../../DMMath/src/DMMath.hpp"

using namespace KDM::math;

template<typename DataIterator, typename SelectType = DataIterator> class BoostKernel : public QDM::IKernel<DataIterator, SelectType>
{
	typedef long int Index;

public:
	BoostKernel() : _countThreads(1) {}

	virtual void where(ResultVector& resultVector, WhereFunction isRequired) const
	{
		std::set<Index> searchSegments = KDM::math::getThreadSegments(resultVector, _countThreads);

		const Index countThreads = (Index)searchSegments.size() - 1;

		std::vector<Index> filterBorders(countThreads);
		std::set<Index>::iterator searchBorder = searchSegments.begin();

		std::vector<boost::thread> searchThreads(countThreads);
		for (Index i = 0; i < countThreads; i++)
		{
			Index leftBorder = *searchBorder++;
			Index rightBorder = *searchBorder;
			searchThreads[i] = boost::thread(KDM::math::search<DataIterator, Index>, boost::ref(resultVector), leftBorder, rightBorder, isRequired, boost::ref(filterBorders[i]));
		}

		for (Index i = 0; i < countThreads; i++)
		{
			searchThreads[i].join();
		}

		KDM::math::eraseExcessElements(resultVector, filterBorders, searchSegments);
	}

	virtual void orderBy(ResultVector& resultVector, OrderByFunction isNeedChange) const
	{
		Index resultVectorSize = (Index)resultVector.size();
		Index left = 0, right = resultVectorSize;

		if (left < right)
		{
			Index segmentsCount = KDM::math::getOrderBySegmentsCount(_countThreads, resultVectorSize);

			std::set<Index> sortBordersSegments;

			sortBordersSegments.insert(left);
			sortBordersSegments.insert(right);

			Index countThreads;

			for (Index i = 1; i < segmentsCount; i *= 2)
			{
				countThreads = (Index)sortBordersSegments.size() - 1;

				std::vector<Index> terminators(countThreads);
				std::vector<boost::thread> sortThreads(countThreads);

				std::set<Index>::iterator border = sortBordersSegments.begin();
				std::vector<DataIterator> medians(countThreads);
				for (Index j = 0; j < countThreads; j++)
				{
					Index leftBorder = *border++;
					Index rightBorder = *border;
					
					sortThreads[j] = boost::thread(hoarSortStep<DataIterator, Index>, boost::ref(resultVector), leftBorder, rightBorder, isNeedChange, boost::ref(terminators[j]), boost::ref(medians[j]));
				}

				for (Index j = 0; j < countThreads; j++)
				{
					sortThreads[j].join();
				}

				for (Index j = 0; j < countThreads; j++)
				{
					sortBordersSegments.insert(terminators[j]);
				}
			}

			std::set<Index>::iterator sortBorder = sortBordersSegments.begin();

			countThreads = (Index)sortBordersSegments.size() - 1;
			
			std::vector<boost::thread> sortThreads(countThreads);

			for (Index i = 0; i < countThreads; i++)
			{
				Index leftBorder = *sortBorder++;
				Index rightBorder = *sortBorder;
				sortThreads[i] = boost::thread(sortSegment<DataIterator, Index>, boost::ref(resultVector), leftBorder, rightBorder, isNeedChange);
			}

			for (Index i = 0; i < countThreads; i++)
			{
				sortThreads[i].join();
			}
		}
	}

	virtual void select(const ResultVector& resultVector, SelectVector& resultSelect, SelectFunction selectFunction) const
	{
		Index resultVectorSize = (Index)resultVector.size();
		resultSelect.resize(resultVectorSize);

		const std::set<Index> selectSegments = KDM::math::getThreadSegments(resultVector, _countThreads);

		Index separaterIndex = 0;
		Index countThreads = (Index)selectSegments.size() - 1;

		std::set<Index>::const_iterator selectSegmentsIterator = selectSegments.cbegin();

		std::vector<Index> borders;

		borders.resize(countThreads + 1);

		for (Index i = 0; i < countThreads + 1; i++)
		{
			borders[i] = *selectSegmentsIterator++;
		}

		std::vector<boost::thread> selectThreads(countThreads);
		for (Index i = 0; i < countThreads; i++)
		{
			selectThreads[i] = boost::thread(KDM::math::select<DataIterator, SelectType, Index>, boost::cref(resultVector), boost::ref(resultSelect), borders[i], borders[i + 1], selectFunction);
		}

		for (Index i = 0; i < countThreads; i++)
		{
			selectThreads[i].join();
		}
	}

	virtual void execute(ResultVector& resultVector, ExecuteFunction executeFunction) const
	{
		std::vector<boost::thread> executeThreads(_countThreads);
		for (Index i = 0; i < _countThreads; i++)
		{
			executeThreads[i] = boost::thread(executeFunction, boost::ref(resultVector), i);
		}
		for (Index i = 0; i < _countThreads; i++)
		{
			executeThreads[i].join();
		}
	}

	void setCountThreads(Index countThreads){_countThreads = countThreads;}
	Index getCountThreads() const {return _countThreads;}

private:

	Index _countThreads;
};

#endif